/**
 * ****************************** ICE *******************************
 * ========================================================================
 * Project:  Redirect Server
 * Package: org.ice4apps.talk.redirect 
 * File:  RedirectServlet.java
 * Author: biju
 * Date: Dec 15, 2009
 * Description:
 * ========================================================================
 */
package org.ice4apps.talk.redirect;

import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Set;
import java.util.TimeZone;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.sip.Address;
import javax.servlet.sip.SipServlet;
import javax.servlet.sip.SipServletContextEvent;
import javax.servlet.sip.SipServletRequest;
import javax.servlet.sip.SipServletResponse;
import javax.servlet.sip.SipServletListener;
import javax.servlet.sip.SipURI;
import javax.servlet.sip.URI;

import org.ice.util.IceLog;
import org.ice.util.Util;
import org.ice4apps.talk.entity.Binding;
import org.ice4apps.talk.entity.Registration;
import org.ice4apps.talk.entity.User;
import org.ice4apps.talk.session.util.URIUtil;
import org.ice4apps.talk.session.security.IceTalkAuthentication;
import org.ice4apps.talk.session.security.MessageDigestResponseAlgorithm;

/**
 * @author Biju
 * This servlet act as a Redirect server.
 */

public class RedirectServlet extends SipServlet implements SipServletListener {
	public static final String CONTACT_HEADER = "Contact";
	public static final String MIN_EXPIRES_HEADER 	= "Min-Expires";
	public static final String CSEQ_HEADER 	= "CSeq";
	public static final String DATE_HEADER 	= "Date";
	
	private int minExpires = 60;
	private int maxExpires = 86400;
	private int defaultExpires = 3600;
	
	private java.text.DateFormat dateFormat;
	 
	public RedirectServlet() {
		IceLog.info(this.getClass(), "Creating RedirectServlet");
		dateFormat = new SimpleDateFormat("EEE, d MMM yyyy HH:mm:ss z", Locale.US);
		dateFormat.setTimeZone(TimeZone.getTimeZone("GMT")); 
	}
	
    public void init(ServletConfig config) throws ServletException {
        super.init(config);
    }    

	public void doRegister(SipServletRequest request) throws ServletException,
			IOException {

		int response = SipServletResponse.SC_MOVED_TEMPORARILY;
		SipServletResponse resp = request.createResponse(response);
		resp.addHeader(CONTACT_HEADER, "");
		try {
			// RC3261, Section 10.3.6 : The registrar checks whether the request contains the Contact header field.  
			// If not, it skips to the last step. 
			Iterator<Address> it = request.getAddressHeaders(CONTACT_HEADER);
			
			String userAlias = URIUtil.instance().getUser(request.getTo().getURI());
			// Check whether this User Alias is valid user if not send 403
			
			String userAgent = (request.getHeader("User-Agent")).toLowerCase();
			// Check for the device from the Register come and if not authorized send 403
			User user = new User ();
//			User user = talkUtil.getUser(userAgent);
			if (null == user)
			{
				int errorResponse = SipServletResponse.SC_FORBIDDEN;
				SipServletResponse errorResp = request.createResponse(errorResponse);
				IceLog.info(this.getClass(), "Registration Forbidden");
				errorResp.send();
				return;
			}
			
			if(it != null) {			
				if (it.hasNext()) {
					List<Address> contacts = new ArrayList<Address>();
					boolean wildcard = false;
					// RC3261, Section 10.3.6 : If the Contact header field is present, the registrar checks if there
			        // is one Contact field value that contains the special value "*" and an Expires field.  
					// If the request has additional Contact fields or an expiration time other than zero, the request is
			        // invalid, and the server MUST return a 400 (Invalid Request) and skip the remaining steps.  				
					while (it.hasNext()) {
						Address contact = it.next();
						if (contact.isWildcard()) {
							wildcard = true;
							if (it.hasNext() || contacts.size() > 0 || request.getExpires() > 0) {
								throw new BadRegistrationException(SipServletResponse.SC_BAD_REQUEST, "invalid wildcard");
							}
						}
						contacts.add(contact);
					}
					
					if (!wildcard)
					{
						resp = IceTalkAuthentication.instance().authenticate(request);
					}
					else 
					{
						//TODO : Add code for wild card here
						int errorResponse = SipServletResponse.SC_FORBIDDEN;
						resp = request.createResponse(errorResponse);
						resp.send();
						return;
					}
					if (200 == resp.getStatus())
					{
						// Create and update the Registration table.
/*						if (!UpdateRegistration (request, user))
						{
							throw new BadRegistrationException(SipServletResponse.SC_BAD_REQUEST, "device not supported");
						}
*/						
					}
					else
					{
						resp.send();
						return;
					}
				}						
			} 	
			else 
			{
				throw new BadRegistrationException(SipServletResponse.SC_BAD_REQUEST, "invalid contact header");
			}
			// RFC3261, Section 10.3.8 : The registrar returns a 200 (OK) response.  
			// The response MUST contain Contact header field values enumerating all current bindings.  
			// Each Contact value MUST feature an "expires" parameter indicating its expiration interval chosen by the registrar.  
			// The response SHOULD include a Date header field.
//			Set<Binding> bindings = registration.getBindings();
			Set<Binding> bindings = null;
			if (bindings != null) {
				for (Binding binding : bindings) {
					resp.addHeader(CONTACT_HEADER, "<" + binding.getContactAddress() + ">;expires=" + binding.getExpires());
				}
			}					
			resp.addHeader(DATE_HEADER, dateFormat.format(new Date()));
			resp.send();
		} catch (BadRegistrationException e) {
			IceLog.info(this.getClass(),"The registration request " + request + " is wrong, cause : " + e.getReason());
			resp = request.createResponse(e.getStatus(), e.getReason());
			resp.send();
		} 
	}
	/**
	 * If the binding does exist, the registrar checks the Call-ID value. 
	 * This algorithm ensures that out-of-order requests from the same UA are ignored.
	 * Each binding record records the Call-ID and CSeq values from the request.
	 * 
	 * If the Call-ID value in the existing binding differs from the Call-ID value in the request, the binding MUST be removed if
	 * the expiration time is zero and updated otherwise.
	 * 
	 * If they are the same, the registrar compares the CSeq value.  If the value
	 * is higher than that of the existing binding, it MUST update or remove the binding as above.  If not, the update MUST be
	 * aborted and the request fails.
	 */
/*	private void updateOrRemoveExistingBinding(
			SipServletRequest request, Registration registration, String callId, int cseq,
			Address contact, int expires, Binding binding) throws BadRegistrationException {
		if (!callId.equals(binding.getCallId())) {
		    // If the Call-ID value in the existing binding differs from the Call-ID value in the request, the binding MUST be removed if
		    // the expiration time is zero and updated otherwise.
			if (expires == 0) {
				removeBinding(registration, binding);
			} else {
				updateBinding(registration, callId, cseq, contact, expires,
						binding);
			}
		} 
		// If they are the same, the registrar compares the CSeq value.  If the value
		// is higher than that of the existing binding, it MUST update or remove the binding as above.  If not, the update MUST be
		// aborted and the request fails.
		else if (cseq > binding.getCSeq()) {
			if (expires == 0) {				
				removeBinding(registration, binding);
			} else {
				updateBinding(registration, callId, cseq, contact, expires,
						binding);
			}
		} else if (cseq < binding.getCSeq()) {
			throw new BadRegistrationException(SipServletResponse.SC_SERVER_INTERNAL_ERROR, "lower cseq");
		}		
	}

*/
	/**
	 * Create and add a binding
	 */
	private void createBinding(Registration registration, String callId,
			int cseq, Address contact, int expires) {
		Binding newBinding = new Binding();
		newBinding.setContactAddress(contact.getURI().toString());
		newBinding.setCallId(callId);
		newBinding.setCSeq(cseq);
		newBinding.setExpires(expires);
		newBinding.setRegistration(registration);
//		registration.addBinding(newBinding);
//		entityManager.persist(newBinding);
//		if(log.isDebugEnabled()) {
//			log.debug("Added binding: " + newBinding);
//		}		
	}
	
	/**
	 * Update a binding
	 */
	private void updateBinding(Registration registration, String callId,
			int cseq, Address contact, int expires, Binding binding) {
		binding.setContactAddress(contact.getURI().toString());
		binding.setCallId(callId);
		binding.setCSeq(cseq);
		binding.setExpires(expires);
//		registration.updateBinding(binding);
//		entityManager.persist(binding);
//		if(log.isInfoEnabled()) {
//			log.info("Updated binding: " + binding);
//		}
	}

	/**
	 * Remove a binding
	 */
	private void removeBinding(Registration registration, Binding binding) {
//		registration.removeBinding(binding);
//		entityManager.remove(binding);
//		if(log.isInfoEnabled()) {
//			log.info("Removed binding: " + binding);
//		}
	}

	/**
	 * For each address, the registrar then searches the list of current bindings using the URI comparison rules.
	 * If the binding does not exist, it is tentatively added. 
	 */
	private Binding findBinding(Set<Binding> bindings, Address contact) {
		if(bindings == null) return null;
		for (Binding binding : bindings) {
			URI contactUri = null;
//			try {
//				contactUri = sipFactory.createURI(binding.getContactAddress());
//			} catch (ServletParseException e) {
//				log.error("Invalid URI present in the registrar: " + binding.getContactAddress(), e);
//				// TODO shall we remove the binding in this case ?
//				return null;
//			}
			if (contact.getURI().equals(contactUri)) {
				return binding;
			}
		}
		return null;
	}

	/**
	 * RFC3261, Section 10.3.5 : The registrar extracts the address-of-record from the To header
	 * field of the request.  If the address-of-record is not valid for the domain in the Request-URI, 
	 * the registrar MUST send a 404 (Not Found) response and skip the remaining steps.  The URI
	 * MUST then be converted to a canonical form.  To do that, all URI parameters MUST be removed (including the user-param), and
	 * any escaped characters MUST be converted to their unescaped form.  
	 *  The result serves as an index into the list of bindings.
	 * @param request the REGISTER request used to extract the address of record and to construct the 404 Not Found
	 * @return the registration if it has been found, null otherwise
	 * @throws IOException if the 404 response could not be sent
	 */
	private Registration processAddressOfRecord(SipServletRequest request)
			throws BadRegistrationException {
		URI toURI = request.getTo().getURI();
		String addressOfRecord = org.ice4apps.talk.session.util.URIUtil.toCanonical(toURI);
		Registration registration = findRegistration(addressOfRecord);
		if(null == registration) {
//			String strict = PbxConfiguration.getProperty("pbx.registration.strict");
//			if("true".equals(strict)) {
//				throw new BadRegistrationException(SipServletResponse.SC_NOT_FOUND, "Address of Record not found");		
//			}
			
			if(request.getFrom().getURI() instanceof SipURI) {
				SipURI uri = (SipURI) request.getFrom().getURI();
				String user = uri.getUser();
				registration = findNotStrictRegistration(user, uri.toString());
				if(registration == null) {
					throw new BadRegistrationException(SipServletResponse.SC_NOT_FOUND, "Address of Record not found");
				}
				//Events.instance().raiseAsynchronousEvent("globalSettingsChanged", (Object[]) null);
			}
		}
		return registration;
	}
	
	/**
	 * RFC 3261, Section 10.3.7 : The registrar determines the expiration interval as follows:
	 * -  If the field value has an "expires" parameter, that value MUST be taken as the requested expiration.
	 * -  If there is no such parameter, but the request has an Expires header field, that value MUST be taken as the requested expiration.
	 * -  If there is neither, a locally-configured default value MUST be taken as the requested expiration.
	 * @param request the request used to create the response
	 * @param contact the contact address to check
	 * @return -1 if no expires was found, otherwise the value
	 * @throws BadRegistrationException if the 423 Interval too Brief response should be sent
	 */
	private int getContactExpiresValue(SipServletRequest request, Address contact) throws BadRegistrationException {
		int expires = contact.getExpires();
		
		if (expires < 0) {
			expires = request.getExpires();
		}
		// The registrar MAY choose an expiration less than the requested
        // expiration interval.  If and only if the requested expiration
        // interval is greater than zero AND smaller than one hour AND
        // less than a registrar-configured minimum, the registrar MAY
        // reject the registration with a response of 423 (Interval Too
        // Brief).  This response MUST contain a Min-Expires header field
        // that states the minimum expiration interval the registrar is
        // willing to honor.  It then skips the remaining steps.
		if (expires != 0) {
			if (expires < 0) {
				expires = defaultExpires;
			}
			if (expires > maxExpires) {
				expires = maxExpires;
			} else if (expires < minExpires) {
				throw new BadRegistrationException(SipServletResponse.SC_INTERVAL_TOO_BRIEF);
			}
		}
		return expires;
	}
	
	public Registration findRegistration(String uri) {
    	List<Registration> registrations = null; 
//    	         = entityManager.createQuery(
//    			"SELECT registration FROM Registration registration WHERE registration.uri = :requestUri")
//    		.setParameter("requestUri", uri).getResultList();
    	
    	if(registrations == null) return null;
    	if(registrations.size() <= 0) return null;
    	
    	Registration reg = registrations.get(0);
    	
    	User user = reg.getUser();
    	
    	if(user == null) return null;
    	
//    	this.user = user;
//    	sipSession.setAttribute("user", user);
    	
        return reg;
    }
	
	public Registration findNotStrictRegistration(String name, String uri) {
    	List<User> users = null;
//    	= entityManager.createQuery(
//    			"SELECT user FROM User user WHERE user.name = :name")
//    		.setParameter("name", name).getResultList();
    	
    	if(users == null) return null;

    	if(users.size() <= 0) return null;
    	
    	User user = users.get(0);
    	
    	Registration reg = new Registration();
    	reg.setUser(user);
//  	reg.setUri(uri);
//  	user.getRegistrations().add(reg);
//    	entityManager.persist(reg);
//    	sipSession.setAttribute("user", user);
    	
        return reg;
    }

	public void servletInitialized(SipServletContextEvent arg0) {
		IceLog.info(this.getClass(), "Registrar Servlet Initialized");
	}
}
